/**
 * Copyright (C) BFH www.bfh.ch 2011
 * Code written by: Patrick Dobler, Marc Folly
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ch.bfh.ti.kybernetik.gui.slick;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.vecmath.Point2d;
import javax.vecmath.Vector2d;

import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import ch.bfh.ti.kybernetik.engine.controller.Simulator;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.SimulatorObserverCommandState;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.LightBulbModelObserverCommand;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.LightBulbModelObserverCommand.LightBulbModelCommandState;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.LightSensorModelObserverCommand;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.LightSensorModelObserverCommand.LightSensorCommandState;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.RoboterModelObserverCommand;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.RoboterModelObserverCommand.RoboterModelCommandState;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.RoboterMoveListModelObserverCommand;
import ch.bfh.ti.kybernetik.engine.controller.observerCommands.model.RoboterMoveListModelObserverCommand.RoboterMoveModelObserverCommandState;
import ch.bfh.ti.kybernetik.engine.model.LightBulb;
import ch.bfh.ti.kybernetik.engine.model.Roboter;
import ch.bfh.ti.kybernetik.gui.slick.components.LightBulbComponent;
import ch.bfh.ti.kybernetik.gui.slick.components.RoboterComponent;
import ch.bfh.ti.kybernetik.gui.slick.components.RoboterMoveListComponent;
import ch.bfh.ti.kybernetik.gui.slick.components.SelectableSlickComponent;
import ch.bfh.ti.kybernetik.lego.ki.RobKIFactr;
import ch.bfh.ti.kybernetik.lego.ki.RobKIFactr.Wesen;
import ch.bfh.ti.kybernetik.lego.ki.RoboterKI;

/**
 * 
 * 
 */
public class GuiSimulator extends BasicGame implements Observer {

	private static final int RENDER_UPDATE_RATE = 60;

	private Simulator simulator;

	private List<RoboterComponent> roboterComponents = new CopyOnWriteArrayList<RoboterComponent>();

	private List<LightBulbComponent> lightBulbComponents = new CopyOnWriteArrayList<LightBulbComponent>();

	private List<RoboterMoveListComponent> roboterMoveListComponent = new CopyOnWriteArrayList<RoboterMoveListComponent>();

	private List<GuiSimulatorListener> guiSimulatorListeners = new CopyOnWriteArrayList<GuiSimulatorListener>();

	private boolean renderTracing = false;

	private enum GuiSimulatorMode {

		REDIRECT_ROBOTS_TO_POINT("Redirect All Robots to Mouse Position"), SLICK_COMPONENT_DRAGDROP("Drag & Drop Component"), ROBOT_DRAGDROP_ROTATION(
				"Set new direction vector");

		private final String displayText;

		GuiSimulatorMode(String displayText) {
			this.displayText = displayText;
		}

		public String getDisplayText() {
			return displayText;
		}
	}

	public void registerGuiSimulatorListener(GuiSimulatorListener guiSimulatorListener) {
		this.guiSimulatorListeners.add(guiSimulatorListener);
	}

	private GuiSimulatorMode mode = null;

	public GuiSimulator(Simulator simulator) {
		super("Kybernetik Simulator");
		this.simulator = simulator;
		this.simulator.addObserver(this);
	}

	@Override
	public void init(final GameContainer gc) throws SlickException {
		simulator.setWidth(gc.getWidth());
		simulator.setHeight(gc.getHeight());
		gc.setAlwaysRender(true);
		gc.setTargetFrameRate(RENDER_UPDATE_RATE);
		gc.setMaximumLogicUpdateInterval(1 / RENDER_UPDATE_RATE);
		gc.setMaximumLogicUpdateInterval(1 / RENDER_UPDATE_RATE);

		Iterator<Roboter> it = simulator.getRoboterList().iterator();
		while (it.hasNext()) {
			Roboter roboter = it.next();
			RoboterComponent rc = new RoboterComponent(roboter, simulator.getRoboterConstructionForRoboter(roboter));
			this.roboterComponents.add(rc);
		}

		Iterator<LightBulb> it2 = simulator.getLightBulbList().iterator();
		while (it2.hasNext()) {
			LightBulb lightBulb = it2.next();
			this.lightBulbComponents.add(new LightBulbComponent(lightBulb));
		}
		this.mode = GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP;
	}

	@Override
	public void update(GameContainer gc, int delta) throws SlickException {
		final Input input = gc.getInput();
		final Point2d mouseClickedPointPosition = new Point2d(input.getMouseX(), input.getMouseY());

		if (input.isKeyPressed(Input.KEY_S)) {
			simulator.setSimulating(true);
		} else if (input.isKeyPressed(Input.KEY_Q)) {
			simulator.setSimulating(false);
		}

		// SETTING MODE
		if (input.isKeyPressed(Input.KEY_F2)) {
			this.mode = GuiSimulatorMode.REDIRECT_ROBOTS_TO_POINT;
		} else if (input.isKeyPressed(Input.KEY_F1)) {
			this.mode = GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP;
			deselectAllSelectableSlickComponents();
		}

		if (input.isKeyDown(Input.KEY_RIGHT)) {
			simulator.setSimulatorSpeed(simulator.getSimulatorSpeed() + 5);
		} else if (input.isKeyDown(Input.KEY_LEFT)) {
			simulator.setSimulatorSpeed(simulator.getSimulatorSpeed() - 5);
		}

		// INTERPRETE MODE
		if (mode == GuiSimulatorMode.REDIRECT_ROBOTS_TO_POINT) {
			if (input.isMouseButtonDown(0)) {
				redirectRobotsToPoint(mouseClickedPointPosition);
			}
		} else if (mode == GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP || mode == GuiSimulatorMode.ROBOT_DRAGDROP_ROTATION) {
			interpreteDragAndDropMode(input, mouseClickedPointPosition);
		}

	}

	private void interpreteDragAndDropMode(final Input input, final Point2d mouseClickedPointPosition) {
		SelectableSlickComponent<?> selectedSlickComponent = getSelectedSlickComponent();

		if (input.isMousePressed(0)) {
			// while the user still holds the mouse button pressed
			SelectableSlickComponent<?> newlySelectedSlickCompontent = selectSlickComponent(mouseClickedPointPosition, false, false);
			SelectableSlickComponent<?> currentSelectedSlickComponent = getSelectedSlickComponent();
			if (newlySelectedSlickCompontent != null && newlySelectedSlickCompontent != currentSelectedSlickComponent) {
				// another slick component was selected
				deselectAllSelectableSlickComponents();
				newlySelectedSlickCompontent.setSelected(true);
				mode = GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP;
				for (GuiSimulatorListener guiSimulator : guiSimulatorListeners) {
					if (newlySelectedSlickCompontent instanceof RoboterComponent) {
						guiSimulator.selctedRoboterComponent((RoboterComponent) newlySelectedSlickCompontent);
					} else if (newlySelectedSlickCompontent instanceof LightBulbComponent) {
						guiSimulator.selctedLightComponent((LightBulbComponent) newlySelectedSlickCompontent);
					}
				}
			} else if (newlySelectedSlickCompontent == null && currentSelectedSlickComponent != null) {
				// nothing was selected
				deselectAllSelectableSlickComponents();
				currentSelectedSlickComponent.setSelected(true);
				mode = GuiSimulatorMode.ROBOT_DRAGDROP_ROTATION;
			} else if (newlySelectedSlickCompontent == currentSelectedSlickComponent) {
				// keep on drag and drop mode for the selected component
				mode = GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP;
			}
		} else if (input.isMouseButtonDown(0)) {
			// the user stopped to press the mouse button
			if (mode == GuiSimulatorMode.SLICK_COMPONENT_DRAGDROP) {
				if (selectedSlickComponent != null) {
					// set new position of the slick component
					selectedSlickComponent.setX(mouseClickedPointPosition.getX());
					selectedSlickComponent.setY(mouseClickedPointPosition.getY());
				}
			} else if (mode == GuiSimulatorMode.ROBOT_DRAGDROP_ROTATION) {
				if (selectedSlickComponent instanceof RoboterComponent) {
					redirectRobotToPoint((RoboterComponent) selectedSlickComponent, mouseClickedPointPosition);
				}
			}
		} else if (input.isKeyPressed(Input.KEY_DELETE)) {
			// the user pressed the DELETE KEY
			if (selectedSlickComponent != null) {
				if (selectedSlickComponent instanceof RoboterComponent) {
					simulator.removeRoboter((Roboter) selectedSlickComponent.getModelObject());
				} else if (selectedSlickComponent instanceof LightBulbComponent) {
					simulator.removeLightBulb((LightBulb) selectedSlickComponent.getModelObject());
				}
			}
		} else if (input.isKeyPressed(Input.KEY_0)) {
			// the user pressed the 0 KEY which changes the RoboterKI
			if (selectedSlickComponent instanceof RoboterComponent) {
				RoboterComponent selectedRoboterComponent = (RoboterComponent) selectedSlickComponent;
				RoboterKI selectedRoboterKI = simulator.getRoboterKIForRoboter(selectedRoboterComponent.getModelObject());
				Wesen[] wesenArray = RobKIFactr.Wesen.values();
				int pos = Arrays.binarySearch(wesenArray, selectedRoboterKI.getWesen());
				if (++pos >= wesenArray.length) {
					pos = 0;
				}
				RoboterKI newRoboterKI = RobKIFactr.getRoboterKI(wesenArray[pos]);
				simulator.changeRoboterKIForRoboter(selectedRoboterComponent.getModelObject(), newRoboterKI);
			}
		}
	}

	private void redirectRobotToPoint(RoboterComponent rc, Point2d mouseClickedPointPosition) {
		Roboter roboter = rc.getModelObject();
		Vector2d vec = new Vector2d(mouseClickedPointPosition.getX() - roboter.getX(), mouseClickedPointPosition.getY() - roboter.getY());
		vec.normalize();
		roboter.setDirection(vec);
	}

	private void redirectRobotsToPoint(Point2d mouseClickedPointPosition) {
		Iterator<RoboterComponent> it = this.roboterComponents.iterator();
		while (it.hasNext()) {
			RoboterComponent rc = it.next();
			Roboter roboter = rc.getModelObject();
			Vector2d vec = new Vector2d(mouseClickedPointPosition.getX() - roboter.getX(), mouseClickedPointPosition.getY()
					- roboter.getY());
			vec.normalize();
			roboter.setDirection(vec);
		}
	}

	private void deselectAllSelectableSlickComponents() {
		List<SelectableSlickComponent<?>> aList = new ArrayList<SelectableSlickComponent<?>>();
		aList.addAll(roboterComponents);
		aList.addAll(lightBulbComponents);

		Iterator<SelectableSlickComponent<?>> it2 = aList.iterator();
		while (it2.hasNext()) {
			SelectableSlickComponent<?> rc = it2.next();
			rc.setSelected(false);
		}

	}

	private SelectableSlickComponent<?> getSelectedSlickComponent() {
		List<SelectableSlickComponent<?>> aList = new ArrayList<SelectableSlickComponent<?>>();
		aList.addAll(roboterComponents);
		aList.addAll(lightBulbComponents);

		Iterator<SelectableSlickComponent<?>> it2 = aList.iterator();
		while (it2.hasNext()) {
			SelectableSlickComponent<?> rc = it2.next();
			if (rc.isSelected())
				return rc;
		}

		return null;
	}

	private SelectableSlickComponent<?> selectSlickComponent(Point2d mouseClickedPointPosition, boolean selectInModel,
			boolean deselectOthers) {
		List<SelectableSlickComponent<?>> aList = new ArrayList<SelectableSlickComponent<?>>();
		aList.addAll(roboterComponents);
		aList.addAll(lightBulbComponents);

		Iterator<SelectableSlickComponent<?>> it2 = aList.iterator();
		boolean selected = false;
		SelectableSlickComponent<?> selectedRoboterComponent = null;
		while (it2.hasNext()) {
			SelectableSlickComponent<?> rc = it2.next();
			if (rc.intersectWithPoint(mouseClickedPointPosition) && selected == false) {
				if (selectInModel)
					rc.setSelected(true);
				selected = true;
				selectedRoboterComponent = rc;
			} else {
				if (deselectOthers)
					rc.setSelected(false);
			}
		}

		return selectedRoboterComponent;
	}

	@Override
	public void render(GameContainer gc, final Graphics g) throws SlickException {

		g.setAntiAlias(true);

		Iterator<RoboterComponent> it = this.roboterComponents.iterator();
		while (it.hasNext()) {
			RoboterComponent rc = it.next();
			rc.render(gc, g);
		}

		Iterator<LightBulbComponent> it2 = this.lightBulbComponents.iterator();
		while (it2.hasNext()) {
			LightBulbComponent lbc = it2.next();
			lbc.render(gc, g);
		}

		if (renderTracing) {
			Iterator<RoboterMoveListComponent> it3 = this.roboterMoveListComponent.iterator();
			while (it3.hasNext()) {
				RoboterMoveListComponent rtc = it3.next();
				rtc.render(gc, g);
			}
		}

		// Render current mode
		g.setColor(Color.green);
		if (mode != null) {
			g.drawString("Mode: " + mode.getDisplayText(), 300, 0);
		}

		// Draw Simulator Speed
		g.drawString("Simulating Speed: " + simulator.getSimulatorSpeed(), 300, 30);

		// Draw Selected Roboter Info
		SelectableSlickComponent<?> ssc = this.getSelectedSlickComponent();
		if (ssc instanceof RoboterComponent) {
			RoboterComponent rc = (RoboterComponent) ssc;
			if (rc != null) {
				RoboterKI roboterKI = simulator.getRoboterKIForRoboter(rc.getModelObject());
				g.drawString("Roboter KI: " + roboterKI.getRoboterKIName(), 300, 60);
			}
		}

	}

	@Override
	public synchronized void update(Observable o, Object observerCommand) {
		if (observerCommand instanceof SimulatorObserverCommandState) {
			SimulatorObserverCommandState simulatorCommand = (SimulatorObserverCommandState) observerCommand;
			if (simulatorCommand == SimulatorObserverCommandState.MOVED_ALL_ROBOTOS) {

			}
		} else if (observerCommand instanceof RoboterModelObserverCommand) {
			RoboterModelObserverCommand rmoc = (RoboterModelObserverCommand) observerCommand;
			if (rmoc.getState() == RoboterModelCommandState.ROBOTER_ADDED) {
				RoboterComponent newRoboterComponent = new RoboterComponent(rmoc.getModel(),
						simulator.getRoboterConstructionForRoboter(rmoc.getModel()));
				this.roboterComponents.add(newRoboterComponent);
			} else if (rmoc.getState() == RoboterModelCommandState.ROBOTER_REMOVED) {
				RoboterComponent rc = findOrAddRoboterComponent(rmoc.getModel());
				this.roboterComponents.remove(rc);
			}
		} else if (observerCommand instanceof LightSensorModelObserverCommand) {
			LightSensorModelObserverCommand lsmoc = (LightSensorModelObserverCommand) observerCommand;
			if (lsmoc.getState() == LightSensorCommandState.SENSOR_INTENSITY_CHANGED) {
				RoboterComponent rc = findOrAddRoboterComponent(lsmoc.getModel());
				rc.hightLightRoboterElement(lsmoc.getPayLoad());
			} else if (lsmoc.getState() == LightSensorCommandState.SENSOR_INTENSITY_REMAINS) {
				RoboterComponent rc = findOrAddRoboterComponent(lsmoc.getModel());
				rc.unHightLightRoboterElement(lsmoc.getPayLoad());
			}
		} else if (observerCommand instanceof RoboterMoveListModelObserverCommand) {
			RoboterMoveListModelObserverCommand rmlmoc = (RoboterMoveListModelObserverCommand) observerCommand;
			if (rmlmoc.getState() == RoboterMoveModelObserverCommandState.ROBOTER_MOVE_ADDED) {
				RoboterMoveListComponent rtc = findOrAddRoboterTraceComponent(rmlmoc.getModel());
				rtc.setMoves(rmlmoc.getPayLoad());
			}
		} else if (observerCommand instanceof LightBulbModelObserverCommand) {
			LightBulbModelObserverCommand lbmoc = (LightBulbModelObserverCommand) observerCommand;
			if (lbmoc.getState() == LightBulbModelCommandState.LIGHTBULB_ADDED) {
				this.lightBulbComponents.add(new LightBulbComponent(lbmoc.getModel()));
			} else if (lbmoc.getState() == LightBulbModelCommandState.LIGHTBULB_REMOVED) {
				LightBulbComponent rc = findOrAddLightBulbComponent(lbmoc.getModel());
				this.lightBulbComponents.remove(rc);
			}
		}
	}

	private LightBulbComponent findOrAddLightBulbComponent(LightBulb lightBulb) {
		Iterator<LightBulbComponent> it = this.lightBulbComponents.iterator();
		while (it.hasNext()) {
			LightBulbComponent rc = it.next();
			if (rc.getModelObject() == lightBulb) {
				return rc;
			}
		}
		LightBulbComponent rc = new LightBulbComponent(lightBulb);
		this.lightBulbComponents.add(rc);
		return rc;
	}

	private RoboterComponent findOrAddRoboterComponent(Roboter roboter) {
		Iterator<RoboterComponent> it = this.roboterComponents.iterator();
		while (it.hasNext()) {
			RoboterComponent rc = it.next();
			if (rc.getModelObject() == roboter) {
				return rc;
			}
		}
		RoboterComponent rc = new RoboterComponent(roboter, simulator.getRoboterConstructionForRoboter(roboter));
		this.roboterComponents.add(rc);
		return rc;
	}

	private RoboterMoveListComponent findOrAddRoboterTraceComponent(Roboter roboter) {
		Iterator<RoboterMoveListComponent> it = this.roboterMoveListComponent.iterator();
		while (it.hasNext()) {
			RoboterMoveListComponent rtc = it.next();
			if (rtc.getRoboter() == roboter) {
				return rtc;
			}
		}
		RoboterMoveListComponent rtc = new RoboterMoveListComponent(roboter);
		rtc.setColor(findOrAddRoboterComponent(roboter).getColor());
		this.roboterMoveListComponent.add(rtc);
		return rtc;
	}

	public void setRenderTracing(boolean renderTracing) {
		this.renderTracing = renderTracing;
	}

	public boolean isRenderTracing() {
		return renderTracing;
	}
}